package ua.yandex.shad.tempseries;

import Jama.*;

public class TemperatureSeriesAnalysis {
    
    private final double[] temperatureSeries;
    public static final double minValueInSeries=-273;
    public static final double minNumberOfTempsForForecast=10;
    
    public TemperatureSeriesAnalysis(double[] temperatureSeries) throws IllegalTemperatureValueException{
        for(int i=0; i<temperatureSeries.length; i++){
            if(temperatureSeries[i]<minValueInSeries) {
                throw new IllegalTemperatureValueException();
            }
        }
        this.temperatureSeries = new double[temperatureSeries.length];
        System.arraycopy(temperatureSeries, 0, this.temperatureSeries, 0, temperatureSeries.length);
    }
    
    private Boolean SpecialCase(){
        return (temperatureSeries.length==0);
    }
    
    public double calcAverageTemp(){
        if(SpecialCase()){
            return 0;
        }
        double sum = 0;
        for (double d : temperatureSeries){
            sum += d/temperatureSeries.length;
        }
        return sum;
    }    
    
    public double calcTempDeviation(){
        if(SpecialCase()){
            return 0;
        }
        double sum =0;
        double averageTemp=this.calcAverageTemp();
        for(double d : temperatureSeries){
            sum+=Math.pow(d-averageTemp, 2)/temperatureSeries.length;
        }
        return sum;
    }
    
    public double findTempClosestToZero(){
        return findTempClosestToValue(0);
    }
    
    private double max(double a, double b){
        return ( a > b ) ? a : b;
    }
    
    public double findTempClosestToValue(double tempValue){
        if(SpecialCase()){
            return 0;
        }
        double deflaction=Math.abs(temperatureSeries[0]-tempValue);
        double tempClosetToValue=temperatureSeries[0];
        for(int i=0; i<temperatureSeries.length; i++)
            if(Math.abs(temperatureSeries[i]-tempValue)<deflaction){
                deflaction=Math.abs(temperatureSeries[i]-tempValue);
                tempClosetToValue=temperatureSeries[i];
            }
            else{
                if(Math.abs(temperatureSeries[i]-tempValue)==deflaction){
                    tempClosetToValue=max(tempClosetToValue, temperatureSeries[i]);
                }
            }
        return tempClosetToValue;
}
    
    public double[] findTempsLessThen(double tempValue){
        int numberOfGoodTemps=0;
        for(double d : temperatureSeries){
            if(d<tempValue) numberOfGoodTemps++;
        }
        double [] tempsLessThenValue=new double[numberOfGoodTemps];
        int numberOfGoodTempsChecked=0;
        for(double d : temperatureSeries){
            if(d<tempValue) {
                tempsLessThenValue[numberOfGoodTempsChecked]=d;
                numberOfGoodTempsChecked++;
            }
        }
        return tempsLessThenValue;
    }
    
    public double[] findTempsGreaterThen(double tempValue){
        int numberOfGoodTemps=0;
        for(double d : temperatureSeries){
            if(d>tempValue){
                numberOfGoodTemps++;
            }
        }
        double [] tempsLessThenValue=new double[numberOfGoodTemps];
        int numberOfGoodTempsChecked=0;
        for(double d : temperatureSeries){
            if(d>tempValue){
                tempsLessThenValue[numberOfGoodTempsChecked]=d;
                numberOfGoodTempsChecked++;
            }
        }
        return tempsLessThenValue;
    }
    
    public double[] forecatsTemp() throws NotEnoughValuesToForecastException{
        if(temperatureSeries.length<minNumberOfTempsForForecast){
            throw new NotEnoughValuesToForecastException();
        }
        
        int heightOfTrajectory=temperatureSeries.length/2;
        int size=temperatureSeries.length;
        int widthOfTrajectory=size-heightOfTrajectory+1;
        
        Matrix trajectory=new Matrix( heightOfTrajectory, widthOfTrajectory);
        for(int i=0; i<heightOfTrajectory; i++){
            for(int j=0; j<widthOfTrajectory; j++){
                trajectory.set(i, j, temperatureSeries[i+j]);
            }
        }
        
        Matrix covariance=((trajectory).times(trajectory.transpose())).times(1.0/widthOfTrajectory);
        Matrix diagonal= new SingularValueDecomposition(covariance).getS();
        Matrix orthogonal= new SingularValueDecomposition(covariance).getU();
        
        int numberOfBigEigenvalues=0;
        for(int i=0; i<heightOfTrajectory; i++){
            if(diagonal.get(i, i)>0.000001){
                numberOfBigEigenvalues++;
            } 
        }
        Matrix V=new Matrix(heightOfTrajectory-1, numberOfBigEigenvalues);
        for(int i=0; i<heightOfTrajectory-1; i++){
            for(int j=0; j<numberOfBigEigenvalues; j++){
                V.set(i, j, orthogonal.get(i, j));
            }
        }
        
        Matrix Q=new Matrix(heightOfTrajectory-1, 1);
        for(int i=size-heightOfTrajectory+1; i<size; i++){
            Q.set(i-size+heightOfTrajectory-1, 0, temperatureSeries[i]);
        }
        
        Matrix w=new Matrix(1,numberOfBigEigenvalues);
        for(int i=0; i<numberOfBigEigenvalues; i++){
            w.set(0, i, orthogonal.get(heightOfTrajectory-1, i));
        }
        
        Matrix mForecastTerm1=w.times(V.transpose().times(V).inverse().times(V.transpose()).times(Q));
        double forecastTerm1=mForecastTerm1.get(0, 0);
        
        for(int i=size-heightOfTrajectory+2; i<size; i++){
            Q.set(i-size+heightOfTrajectory-2, 0, temperatureSeries[i]);
        }
        Q.set(heightOfTrajectory-2, 0, forecastTerm1);
        Matrix mForecastTerm2=w.times(V.transpose().times(V).inverse().times(V.transpose()).times(Q));
        double forecastTerm2=mForecastTerm2.get(0, 0);
        
        for(int i=size-heightOfTrajectory+3; i<size; i++){
            Q.set(i-size+heightOfTrajectory-3, 0, temperatureSeries[i]);
        }
        Q.set(heightOfTrajectory-3, 0, forecastTerm1);
        Q.set(heightOfTrajectory-2, 0, forecastTerm2);
        Matrix mForecastTerm3=w.times(V.transpose().times(V).inverse().times(V.transpose()).times(Q));
        double forecastTerm3=mForecastTerm3.get(0, 0);
        
        double[] result=new double[size+3];
        System.arraycopy(temperatureSeries, 0, result, 0, size);
        result[size]=forecastTerm1;
        result[size+1]=forecastTerm2;
        result[size+2]=forecastTerm3;
        
        return result;
    }
   
}
